O Python provê algumas ferramentas para avaliar performance e localizar gargalos na aplicação. Entre estas ferramentas estão os módulos cProfile e timeit.
O módulo cProfile faz uma análise detalhada de performance, incluindo as chamadas de função, retornos de função e exceções.
Exemplo:
In [1]:
import cProfile
def rgb1():
"""
Função usando range()
"""
rgbs = []
for r in range(256):
for g in range(256):
for b in range(256):
rgbs.append('#%02x%02x%02x' % (r, g, b))
return rgbs
def rgb2():
"""
Função usando xrange()
"""
rgbs = []
for r in xrange(256):
for g in xrange(256):
for b in xrange(256):
rgbs.append('#%02x%02x%02x' % (r, g, b))
return rgbs
def rgb3():
"""
Gerador usando xrange()
"""
for r in xrange(256):
for g in xrange(256):
for b in xrange(256):
yield '#%02x%02x%02x' % (r, g, b)
def rgb4():
"""
Função usando uma lista várias vezes
"""
rgbs = []
ints = range(256)
for r in ints:
for g in ints:
for b in ints:
rgbs.append('#%02x%02x%02x' % (r, g, b))
return rgbs
def rgb5():
"""
Gerador usando apenas uma lista
"""
for i in range(256 ** 3):
yield '#%06x' % i
def rgb6():
"""
Gerador usando xrange() uma vez
"""
for i in xrange(256 ** 3):
yield '#%06x' % i
# Benchmarks
print 'rgb1:'
cProfile.run('rgb1()')
print 'rgb2:'
cProfile.run('rgb2()')
print 'rgb3:'
cProfile.run('list(rgb3())')
print 'rgb4:'
cProfile.run('rgb4()')
print 'rgb5:'
cProfile.run('list(rgb5())')
print 'rgb6:'
cProfile.run('list(rgb6())')
O relatório do cProfile mostra no inicio as duas informações mais importantes: o tempo de CPU consumido em segundos e a quantidade de chamadas de função. As outras linhas mostram os detalhes por função, incluindo o tempo total e por chamada.
As cinco rotinas do exemplo têm a mesma funcionalidade: geram uma escala de cores RGB. Porém, o tempo de execução é diferente.
Comparando os resultados:
Rotina | Tipo | Tempo | Laços | x/range() |
---|---|---|---|---|
rgb1() |
Função | 24.060 | 3 | range() |
rgb2() |
Função | 23.214 | 3 | xrange() |
rgb3() |
Gerador | 23.711 | 3 | xrange() |
rgb4() |
Função | 23.812 | 3 | range() |
rgb5() |
Gerador | 10.513 | 1 | range() |
rgb6() |
Gerador | 10.369 | 1 | xrange() |
Fatores observados que pesaram no desempenho:
xrange()
apresentou uma performance ligeiramente melhor do que a função range()
.O gerador rgb6()
, que usa apenas um laço e xrange()
, é bem mais eficiente que as outras rotinas.
Outro exemplo:
In [2]:
import cProfile
def fib1(n):
"""
Fibonacci calculado de forma recursiva.
"""
if n > 1:
return fib1(n - 1) + fib1(n - 2)
else:
return 1
def fib2(n):
"""
Fibonacci calculado por um loop.
"""
if n > 1:
# O dicionário guarda os resultados
fibs = {0:1, 1:1}
for i in xrange(2, n + 1):
fibs[i] = fibs[i - 1] + fibs[i - 2]
return fibs[n]
else:
return 1
print 'fib1'
cProfile.run('[fib1(x) for x in xrange(1, 31)]')
print 'fib2'
cProfile.run('[fib2(x) for x in xrange(1, 31)]')
A performance do cálculo da série de Fibonacci usando um laço que preenche um dicionário é muito mais eficiente do que a versão usando recursão, que faz muitas chamadas de função.
O módulo timeit serve para fazer benchmark de pequenos trechos de código. O módulo foi projetado para evitar as falhas mais comuns que afetam programas usados para fazer benchmarks.
Exemplo:
In [3]:
import timeit
# Lista dos quadrados de 1 a 1000
cod = '''s = []
for i in xrange(1, 1001):
s.append(i ** 2)
'''
print timeit.Timer(cod).timeit()
# Com Generator Expression
cod = 'list(x ** 2 for x in xrange(1, 1001))'
print timeit.Timer(cod).timeit()
# Com List Comprehesion
cod = '[x ** 2 for x in xrange(1, 1001)]'
print timeit.Timer(cod).timeit()
O List Comprehension é mais eficiente do que o laço tradicional.
Outra forma de melhorar a performance de uma aplicação é usando o Psyco, que é uma espécie de Just In Time Compiler (JIT). Durante a execução, ele tenta otimizar o código da aplicação e, por isso, o módulo deve ser importado antes do código a ser otimizado (o inicio do módulo principal da aplicação é um lugar adequado).
Exemplo (com o último trecho de código avaliado no exemplo anterior):
In [ ]:
import psyco
# Tente otimizar tudo
psyco.full()
import timeit
# Lista dos quadrados de 1 a 1000
cod = '[x ** 2 for x in xrange(1, 1001)]'
print timeit.Timer(cod).timeit()
26.678481102
O código foi executado mais de duas vezes mais rápido do que antes. Para isso, foi necessário apenas acrescentar duas linhas de código.
Porém, o Psyco deve ser usado com alguns cuidados, pois em alguns casos ele pode não conseguir otimizar ou até piorar a performance. As funções map()
e filter()
devem ser evitadas e módulos escritos em C, como o re (expressões regulares) devem ser marcados com a função cannotcompile()
para que o Psyco os ignore. O módulo fornece formas de otimizar apenas determinadas partes do código da aplicação, tal como a função profile()
, que só otimiza as partes mais pesadas do aplicativo, e uma função log()
que analisa a aplicação, para contornar estas situações.
Algumas dicas sobre otimização:
In [1]:
Out[1]: